package gapidt.nlp;

import java.io.Serializable;
import java.util.Vector;

/**
 * 
 * @author cuchillo
 * @param <T>
 *
 */
public class Tree<T> implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 4811414963360704920L;
	
	private T data;
	private int repetitions;
	private Vector<Tree<T>> childs;
	private Vector<Tree<T>> parents;
	
	public Tree(T data){
		this.data=data;
		this.repetitions=1;
		this.childs=new Vector<Tree<T>>();
		this.parents=new Vector<Tree<T>>();
	}
	/**
	 * Agrega un hijo nuevo
	 * @param i
	 * @param child
	 */
	public synchronized void addChild(int i,Tree<T> child){
		this.childs.add(i,child);
		child.parents.add(this);
	}
	/**
	 * Agrega un hijo
	 * @param child
	 */
	public synchronized void addChild(Tree<T> child){
		this.addChild(this.childs.size(), child);
	}
	/**
	 * Remueve al hijo
	 * @param child
	 */
	public synchronized void removeChild(Tree<T> child){
		this.childs.remove(child);
		child.parents.remove(this);
	}
	/**
	 * Agrega un hijo, si ya se encuentra suma la cantidad de ocurrencias
	 * @param child
	 */
	public synchronized void mergeChilds(Tree<T> child){
		for(Tree<T> aux:this.childs){
			//Si se encuentra el nodo se suma la cantidad de ocurrencias
			if(aux.getData().equals(child.getData())){
				aux.repetitions++;
				//Se siguen agregando los hijos de los hijos
				for(Tree<T> nextChild:child.getChilds())
					aux.mergeChilds(nextChild);
				return;
			}
		}
		this.addChild(child);
	}
	/**
	 * Retorna true si el árbol no tiene ramas
	 * @return
	 */
	public boolean isLeaf(){
		return (this.childs.size()==0);
	}
	/**
	 * Obtiene las hojas del árbol
	 * @return
	 */
	public Vector<Tree<T>> getLeaves(){
		Vector<Tree<T>> result=new Vector<Tree<T>>();
		if(this.isLeaf()){
			result.add(this);
		}
		else{
			for(Tree<T> child:this.childs){
				for(Tree<T> aux:child.getLeaves()){
					if(!result.contains(aux))result.add(aux);
				}
			}
		}
		return result;
	}
	/**
	 * Retorna la profundidad maxima del árbol
	 * @return
	 */
	public int getMaxDeep(){
		int max=0;
		for(Tree<T> child:this.childs){
			int deep=child.getMaxDeep();
			if(deep>max) max=deep;
		}
		return max+1;
	}
	/**
	 * Retorna el camino de maxima profundidad
	 * @return
	 */
	public Vector<Integer> getMaxDeepPath(){
		Vector<Integer> result=new Vector<Integer>();
		for(int i=0;i<this.childs.size();i++){
			Tree<T> child=this.childs.elementAt(i);
			Vector<Integer>  deep=child.getMaxDeepPath();
			if(deep.size()>(result.size()-1)){
				result=deep;
				result.add(0, i);
			}
		}
		return result;
	}
	/**
	 * Retorna la profundidad a la hoja más cercana
	 * @return
	 */
	public int getMinDeep(){
		return this.getMinDeep(1,Integer.MAX_VALUE);
	}
	
	private int getMinDeep(int current, int minDeep){
		int md=minDeep;
		for(Tree<T> child:this.childs){
			int deepF=child.getMinDeep(current+1, md);
			int aux=current+deepF;
			if(aux<md) md=aux;
		}
		return md;
	}
	/**
	 * Retorna el camino al hijo más cercano
	 * @return
	 */
	public Vector<Integer> getMinDeepPath(){
		return this.getMinDeepPath(1,Integer.MAX_VALUE);
	}
	
	private Vector<Integer> getMinDeepPath(int current, int minDeep){
		int md=minDeep;
		Vector<Integer> result=new Vector<Integer>();
		for(int i=0;i<this.childs.size();i++){
			Tree<T> child=this.childs.elementAt(i);
			Vector<Integer> deepF=child.getMinDeepPath(current+1, md);
			int aux=current+(deepF.size()+1);
			if(aux<md){
				md=aux;
				result=deepF;
				result.add(0,i);
			}
		}
		return result;
	}
	/**
	 * Return average deep
	 * @return
	 */
	public double getAverageDeep(){
		Vector<Integer> res=this.deeps();
		double cant=0;
		for(int i:res){
			cant+=i;
		}
		return cant/((double)res.size());
	}
	
	private Vector<Integer> deeps(){
		Vector<Integer> result=new Vector<Integer>();
		for(Tree<T> child:this.childs){
			Vector<Integer> res=child.deeps();
			for(int i:res)
				result.add(i+1);
		}
		if(result.size()==0) result.add(1);
		return result;
	}
	
	public T getData() {
		return data;
	}
	public void setData(T data) {
		this.data = data;
	}
	public Vector<Tree<T>> getChilds() {
		return childs;
	}
	public Vector<Tree<T>> getParents() {
		return parents;
	}
	public int getRepetitions() {
		return repetitions;
	}
	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object arg0) {
		if(!arg0.getClass().isInstance(this))
			return false;
		Tree<T> arg=(Tree<T>)arg0;
		return ((this.data.equals(arg.data))&&
				(this.childs.equals(arg.childs))&&
				(this.parents.equals(arg.parents)));
	}
	@Override
	public String toString() {
		StringBuffer result=new StringBuffer("-");
		result.append(data.toString());
		result.append(" (");
		result.append(this.repetitions);
		result.append(")");
		for(Tree<T> child:this.childs){
			result.append("\n| ");
			result.append(child.toString().replaceAll("\n", "\n| "));
		}
		return result.toString();
	}
	
	public boolean contains(T element){
		boolean result=this.data.equals(element);
		for(int i=0;(i<this.childs.size())&&!result;i++)
			result=this.childs.get(i).contains(element);
		return result;
	}
	
	/**
	 * Devuelve la cantidad de nodos del árbol
	 * @return int
	 */
	public int getNodeCount(){
		if(this.isLeaf())
			return 1;
		else{
			int childsNodeCount = 0;
			for(Tree<T> child : this.getChilds())
				childsNodeCount += child.getNodeCount();
			return childsNodeCount + 1;
		}
	}
}
